
#include "stdafx.h"
#include "usbcomm.h"
#include "IRCReceiver.h"

CIRCReceiver CCaoGadgetsDotCom::m_ircReceiver;
HWND CCaoGadgetsDotCom::m_hwndStatus;
bool CCaoGadgetsDotCom::m_enableEmails;

CThisMap CCaoGadgetsDotCom::m_thisMap; 

CUsbComm::CUsbComm() {

		m_master_ids[0]=0xB1;
		m_master_ids[1]=0xB2;
		m_master_ids[2]=0xB3;
		m_attached=false;
		m_radio_on=false;
		m_listening_mode=false;
		
		m_hEventThreadReady = CreateEvent(NULL, TRUE, FALSE, NULL);
		m_hEventAllWritesDone = CreateEvent(NULL, TRUE, TRUE, NULL);
		m_outstandingWrites =0;
}
CUsbComm::~CUsbComm(){
		CloseHandle(m_hEventThreadReady);
		CloseHandle(m_hEventAllWritesDone);
}

void CUsbComm::PopNotificationWnd(){
	if(m_notification_wnd_stack.empty())return;
	UnregisterDeviceNotification(m_notification_wnd_stack.back().m_hdevnotify_hid);
	//UnregisterDeviceNotification(m_notification_wnd_stack.back().m_hdevnotify_disk);
	m_notification_wnd_stack.pop_back();
}

void CUsbComm::PushNotificationWnd(HWND hwnd){
	NotificationWnd not;

	DEV_BROADCAST_DEVICEINTERFACE MyDeviceBroadcastHeader;
	MyDeviceBroadcastHeader.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
	MyDeviceBroadcastHeader.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
	MyDeviceBroadcastHeader.dbcc_reserved = 0;
	
	MyDeviceBroadcastHeader.dbcc_classguid = DEVINTERFACE_HID;
	not.m_hdevnotify_hid=RegisterDeviceNotification(hwnd, &MyDeviceBroadcastHeader, DEVICE_NOTIFY_WINDOW_HANDLE);

	//MyDeviceBroadcastHeader.dbcc_classguid = DEVINTERFACE_DISK;
	//not.m_hdevnotify_disk=RegisterDeviceNotification(hwnd, &MyDeviceBroadcastHeader, DEVICE_NOTIFY_WINDOW_HANDLE);

	not.m_hwnd = hwnd;
	m_notification_wnd_stack.push_back(not);
}


void CUsbComm::detachDevice(){
	if (m_attached)
	{
		if(IsListening()){
			StopListening();
		}
						
		PostThreadMessage(m_idThread, WM_ABORT_READ_THREAD, 0,0);
		m_attached = false;
		m_radio_on = false;
		m_listening_mode = false;

		WaitForSingleObjectEx(m_hThread, 5000, TRUE);
	}
}

DWORD CUsbComm::attachDevice(CString DeviceIDToFind)
{
	if(m_attached)
		return ERROR_SUCCESS;

	// Initialise required datatypes and variables
	HDEVINFO hdev;
	SP_DEVICE_INTERFACE_DATA idata; 
	SP_DEVINFO_DATA DevInfoData;

	DWORD InterfaceIndex = 0;
	DWORD StatusLastError = 0;
	DWORD dwRegType;
	DWORD dwRegSize;
	PBYTE PropertyValueBuffer;
	DWORD ErrorStatus;
	
	m_anotherStickDetected=false;

	// Here we populate a list of plugged-in devices matching our class GUID (DIGCF_PRESENT specifies that the list
	// should only contain devices which are plugged in)
	hdev = SetupDiGetClassDevs(&DEVINTERFACE_HID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

	// Look through the retrieved list of class GUIDs looking for a match on our interface GUID
	do
	{
		idata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		if (SetupDiEnumDeviceInterfaces(hdev, NULL, &DEVINTERFACE_HID, InterfaceIndex, &idata))
		{
			// Check for an error
			ErrorStatus = GetLastError();

			// Are we at the end of the list?
			if (ERROR_NO_MORE_ITEMS == ErrorStatus)
			{
 				// Device is not attached, clean up memory and return with error status
				SetupDiDestroyDeviceInfoList(hdev);
				m_attached = false;
				return ErrorStatus;
			}
		}
		else
		{
			// An unknown error occurred! Clean up memory and return with error status
			ErrorStatus = GetLastError();
			SetupDiDestroyDeviceInfoList(hdev);
			m_attached = false; 
			return ErrorStatus;
		}

		// Now we have devices with a matching class GUID and interface GUID we need to get the hardware IDs for 
		// the devices.  From that we can get the VID and PID in order to find our target device.

		DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
		SetupDiEnumDeviceInfo(hdev, InterfaceIndex, &DevInfoData);

		SetupDiGetDeviceRegistryProperty(hdev, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, NULL, 0, &dwRegSize);
		PropertyValueBuffer = (BYTE *) malloc (dwRegSize);

		// Test to ensure the memory was allocated:
		if(PropertyValueBuffer == NULL)
		{
			// Not enough memory... clean up and return error status
			SetupDiDestroyDeviceInfoList(hdev);
			m_attached = false; 
			return ERROR_NO_SYSTEM_RESOURCES;
		}

		// Get the hardware ID for the current device.  The PropertyValueBuffer gets filled with an array
		// of NULL terminated strings (REG_MULTI_SZ).  The first string in the buffer contains the 
		// hardware ID in the format "Vid_xxxx&Pid_xxxx" so we compare that against our target device
		// identifier to see if we have a match.
		SetupDiGetDeviceRegistryProperty(hdev, &DevInfoData, SPDRP_HARDWAREID, &dwRegType,
			PropertyValueBuffer, dwRegSize, NULL);

		CString DeviceIDFromRegistry;
		DeviceIDFromRegistry=((TCHAR*)PropertyValueBuffer);
		free(PropertyValueBuffer);

		DeviceIDFromRegistry.MakeLower();
		DeviceIDToFind.MakeLower();

		if(DeviceIDFromRegistry.Find(DeviceIDToFind) != -1)
		{
			DWORD StructureSize = 0;
	
			SetupDiGetDeviceInterfaceDetail(hdev, &idata, NULL, NULL, &StructureSize, NULL);
			PSP_DEVICE_INTERFACE_DETAIL_DATA pdidata = (PSP_DEVICE_INTERFACE_DETAIL_DATA)(malloc(StructureSize));

			if(pdidata == NULL)
			{
				SetupDiDestroyDeviceInfoList(hdev);
				m_attached = false;
				return ERROR_NO_SYSTEM_RESOURCES;
			}

			pdidata->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
			SetupDiGetDeviceInterfaceDetail(hdev, &idata, pdidata, StructureSize, NULL, NULL); 

			m_usbHandle = CreateFile((pdidata->DevicePath),
				GENERIC_READ|GENERIC_WRITE,0/*FILE_SHARE_WRITE|FILE_SHARE_READ*/, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING|FILE_FLAG_OVERLAPPED, 0);
			ErrorStatus = GetLastError();
			
			free(pdidata);

			if(ErrorStatus == ERROR_SUCCESS)
			{
				SetupDiDestroyDeviceInfoList(hdev);
				
				m_outstandingWrites=0;
				SetEvent(m_hEventAllWritesDone);

				m_hThread = CreateThreadT<CUsbComm>(NULL, 0, ThreadProc, this, 0, &m_idThread);
				if(m_hThread){
					
					WaitForSingleObject(m_hEventThreadReady, INFINITE);
					
					m_attached = true;
					return ERROR_SUCCESS;
				}

			}else if(ErrorStatus == ERROR_SHARING_VIOLATION){
				m_anotherStickDetected = true;
			}

			CloseHandle(m_usbHandle);
			//CloseHandle(m_readHandle);
		}
	}
	while(InterfaceIndex++<10000);

	m_attached = false;
	return ERROR_NO_MORE_ITEMS;
}

bool CUsbComm::SendPacketAsync(UsbPacket_OUT* pPacket)
{
	if (m_attached == false)
	{
		return false;
	}
	OVERLAPPED_ex* ov = new OVERLAPPED_ex;
	ov->ov.Offset=ov->ov.OffsetHigh = 0;
	ov->ov.hEvent=0;
	ov->packet_start=0;
	ov->obj=this;
	memcpy(&ov->packet, pPacket, sizeof(UsbPacket_OUT));
	BOOL ret = PostThreadMessage(m_idThread, WM_WRITE_ASYNC, 0,(LPARAM)ov);
	//MyTrace(_T("PostThreadMessage WM_WRITE_ASYNC cmd=0x%x ret=%d.\n"), pPacket->cmd, ret);
	return true;
}

void CALLBACK CUsbComm::WriteCompletionRoutine(DWORD err, DWORD bytesWritten, OVERLAPPED* overlapped)
{
	UsbPacket_OUT& packet = ((OVERLAPPED_ex*)overlapped)->packet;
	if(packet.cmd == CMD_CONFIG){
		MyTrace(_T("WM_WRITE_ASYNC completion routine, config_cmd=0x%x\n"), packet.config_cmd);
	}else{
		if(packet.cmd & CMD_MULTIPLE_MASK){
			MyTrace(_T("WM_WRITE_ASYNC completion routine MULTI cmd = 0x%x, id_begin=%d, id_end=%d\n"),  
						packet.cmd, packet.slave_id_begin_iterate, packet.slave_id_end_iterate);
		}else
			MyTrace(_T("WM_WRITE_ASYNC completion routine, cmd=0x%x\n"), packet.cmd);
	}
	((OVERLAPPED_ex*)overlapped)->obj->decOutstandingWrites();
	delete overlapped;
}

DWORD  CALLBACK CUsbComm::ThreadProc(CUsbComm* obj)
{
	DWORD lastError;
	DWORD read=0;
	BYTE* pPacket;
	OVERLAPPED overlapped;
	overlapped.Offset=overlapped.OffsetHigh=0;
	overlapped.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);
	
	MSG msg;			
	PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);			// create a message queue

	SetEvent(obj->m_hEventThreadReady);

	while(1){
		pPacket = new BYTE[sizeof(UsbPacket_IN)+1];
		ZeroMemory(pPacket, sizeof(UsbPacket_IN)+1);
		if(!ReadFile(obj->m_usbHandle, pPacket, sizeof(UsbPacket_IN)+1, &read, &overlapped)){
			lastError = GetLastError();
			if(ERROR_IO_PENDING != lastError){
				obj->m_notification_wnd_stack.PostMessage(WM_RECEIVE_USB_ERROR, 0, (LPARAM)lastError);	   // recipient is expected to call detachDevice(). 
				ResetEvent(overlapped.hEvent);  // so that we wait message without repeating readfile
			}
		}else{
			MyTrace(_T("ReadFile No Wait.\n"));
			obj->m_notification_wnd_stack.PostMessage(WM_RECEIVE_USB_PACKET, 0, (LPARAM)pPacket);
			continue;
		}

		while(1){
			DWORD ret = MsgWaitForMultipleObjectsEx(1, &overlapped.hEvent, INFINITE, QS_POSTMESSAGE, MWMO_ALERTABLE);

			if(WAIT_OBJECT_0 == ret){
				MyTrace(_T("Read Packet After Wait.\n"));
				obj->m_notification_wnd_stack.PostMessage(WM_RECEIVE_USB_PACKET, 0, (LPARAM)pPacket);
				ResetEvent(overlapped.hEvent);
				break;
			}else if(WAIT_OBJECT_0+1 == ret){
				
				while(PeekMessage(&msg, NULL, 0,0,PM_REMOVE))
				{
					if(msg.message == WM_ABORT_READ_THREAD)
					{
						ResetEvent(obj->m_hEventThreadReady);

						MyTrace(_T("Closing USB Handle\n"));
						
						DWORD ret;
						while(WAIT_IO_COMPLETION == (ret = obj->waitForAllWritesDone(5000)));
						//CancelIo(obj->m_usbHandle);
						CloseHandle(obj->m_usbHandle);
						
						MyTrace(_T("Closed USB Handle, wait ret = 0x%x\n"), ret);
						
						CloseHandle(overlapped.hEvent);
						delete pPacket;
						ExitThread(0);
					}
					else if(msg.message == WM_WRITE_ASYNC)
					{
						OVERLAPPED_ex* ov = (OVERLAPPED_ex*)msg.lParam;

						if(ov->packet.cmd == CMD_CONFIG){
							MyTrace(_T("WM_WRITE_ASYNC, config_cmd=0x%x"), ov->packet.config_cmd);
						}else{
							MyTrace(_T("WM_WRITE_ASYNC, cmd=0x%x"), ov->packet.cmd);
						}
						if(!WriteFileEx(obj->m_usbHandle, &ov->packet_start, sizeof(UsbPacket_OUT)+1, &ov->ov, WriteCompletionRoutine))
						{
							lastError = GetLastError();
							if(ERROR_IO_PENDING != lastError){
								obj->m_notification_wnd_stack.PostMessage(WM_WRITE_USB_ERROR, 0, (LPARAM)lastError);	
								MyTrace(_T(" ... FAILED %d. DELETING ov. \n"), lastError);
								delete ov;
							}else{
								MyTrace(_T(" ... IO_PENDING. \n"), lastError);
								obj->incOutstandingWrites();
							}

						}else{
							MyTrace(_T(" ... SUCCEEDED. \n"), lastError);
							obj->incOutstandingWrites();
						}
					}
				}
			}
		}
	}
}
